Skip to content

[FEAT] Suspense/ErrorBoundary 공통 래퍼(AsyncBoundary) 추가#115

Merged
kimminna merged 2 commits into
developfrom
feat/web/114-async-boundary
Jul 8, 2026
Merged

[FEAT] Suspense/ErrorBoundary 공통 래퍼(AsyncBoundary) 추가#115
kimminna merged 2 commits into
developfrom
feat/web/114-async-boundary

Conversation

@kimminna

@kimminna kimminna commented Jul 7, 2026

Copy link
Copy Markdown
Member

ISSUE 🔗

close #114



What is this PR? 🔍

Suspense/ErrorBoundary를 조합한 공통 AsyncBoundary를 추가하고, 이후 리뷰에서 지적된 타입 중복·에러 처리 안전성 문제를 함께 수정했습니다.

배경

  • 기존 구조: Suspense나 ErrorBoundary가 필요한 컴포넌트마다 매번 인라인으로 <Suspense>를 작성하거나, 에러 처리는 아예 하지 않는 구조였습니다.
  • 발생 문제: useSearchParams()처럼 Next.js가 Suspense 경계를 요구하는 클라이언트 훅을 쓸 때마다 반복 작업이 필요했고, 향후 실제 비동기 데이터 페칭(React Query suspense 모드 등)을 붙일 때 로딩/에러 처리가 표준화되어 있지 않았습니다.
  • 해결 방향: Suspense와 ErrorBoundary를 조합한 공통 AsyncBoundary 컴포넌트를 만들어, 에러 처리가 필요 없는 경우와 필요한 경우를 하나의 컴포넌트로 함께 지원하도록 했습니다.

언제 · 왜 사용하는가

  • Suspense만 필요할 때: useSearchParams() 등 Next.js가 Suspense 경계를 강제하는 클라이언트 훅을 쓰는 컴포넌트를 감쌀 때 errorFallback 없이 <AsyncBoundary pendingFallback={...}>로 감싸면 됩니다. 이 경우 내부적으로 <Suspense>만 적용되고 ErrorBoundary는 렌더링에 추가되지 않습니다.
  • 에러 처리까지 필요할 때: 향후 React Query suspense 모드 등 실제로 실패할 수 있는 비동기 데이터 페칭을 붙이는 컴포넌트는 errorFallback을 함께 넘겨야 합니다. errorFallback이 있으면 <Suspense> 바깥을 <ErrorBoundary>로 한 번 더 감싸, 로딩 중 실패와 렌더링 중 예외를 모두 fallback UI로 처리하고 reset()으로 재시도할 수 있게 합니다.
  • 필요한 이유: 두 경계를 매번 따로 조합하면 팀마다 감싸는 순서·prop 이름이 갈리기 쉽고, 에러 처리를 빼먹은 채 Suspense만 적용해 화면이 하얗게 죽는 경우가 생깁니다. AsyncBoundary 하나로 표준화해 이런 누락을 막습니다.

boundary 컴포넌트 추가

  • 변경 요약: components/boundary/ErrorBoundary.tsx(클래스 컴포넌트)와 components/boundary/AsyncBoundary.tsx(Suspense + optional ErrorBoundary)를 추가했습니다.
  • 이유: React는 훅 기반 ErrorBoundary를 제공하지 않아 getDerivedStateFromError를 쓰는 클래스 컴포넌트가 필요했고, Suspense 전용 래퍼와 Suspense+에러처리 래퍼를 별도 컴포넌트로 나눌지 검토했으나 이름이 비슷해 어떤 걸 써야 할지 헷갈릴 수 있어 하나로 통합했습니다.
  • 구현 방식: AsyncBoundaryerrorFallback prop을 선택적으로 받습니다. errorFallback을 넘기지 않으면 <Suspense fallback={pendingFallback}>만으로 감싸고, 넘기면 그 바깥을 <ErrorBoundary fallback={errorFallback}>로 한 번 더 감쌉니다.
  • 경계 · 제약: 이번 PR은 재사용 가능한 boundary 컴포넌트 추가만 포함하며, 실제 사용처(예: 홈 화면의 검색 파라미터 기반 필터링)는 별도 작업/PR에서 다룹니다.

리뷰 대응: 타입 공유 및 에러 처리 안전성 개선

  • 변경 요약: AsyncBoundaryErrorBoundary의 fallback 타입을 중복 정의하던 것을 export된 공용 타입으로 재사용하도록 바꾸고, ErrorBoundaryError가 아닌 값이 throw되는 경우와 에러 모니터링 누락 문제를 함께 처리했습니다.
  • 이유: AsyncBoundaryErrorBoundary가 각자 동일한 유니온 타입을 따로 선언하고 있어 한쪽만 바뀌면 타입이 어긋날 위험이 있었습니다. 또한 getDerivedStateFromError는 React 타입상 Error만 받는 것처럼 보이지만 실제로는 문자열이나 객체가 그대로 throw될 수 있어 .message 등에 접근하면 런타임 에러로 이어질 수 있었고, 캐치된 에러가 화면 fallback으로만 처리되고 어디에도 리포팅되지 않아 실제 장애를 놓칠 수 있었습니다.
  • 구현 방식: ErrorBoundary.tsx에서 ErrorFallbackTypesexport하고 AsyncBoundary.tsx는 이를 그대로 import해 씁니다. getDerivedStateFromError(error: unknown)componentDidCatch(error: unknown, errorInfo)가 공통 toError() 헬퍼로 값을 Error 인스턴스로 정규화한 뒤, componentDidCatch에서 Sentry.captureException(toError(error), { contexts: { react: { componentStack } } })로 리포팅합니다. 기존 reset()/fallback 렌더링 동작은 그대로 유지했습니다.
  • 경계 · 제약: Sentry 연동은 프로젝트에 이미 구성된 @sentry/nextjs(app/global-error.tsx에서 쓰는 것과 동일한 패턴)를 그대로 사용했습니다.



To Reviewers

errorFallback을 선택적으로 두고 유무에 따라 ErrorBoundary 래핑 여부를 분기한 설계가 적절한지, 그리고 componentDidCatch에서의 Sentry 리포팅 방식이 global-error.tsx의 기존 패턴과 일관되는지 봐주세요. 아직 실제 사용처에 연결하지 않은 상태라 이번 PR만으로는 동작을 눈으로 확인할 수 있는 화면은 없습니다.



Screenshot 📷



Test Checklist ✔

  • pnpm --filter timo-web check-types 통과
  • pnpm --filter timo-web lint 통과
  • 실제 사용처 연동 후 Suspense/에러 폴백 동작 확인 — 후속 작업

useSearchParams 등 클라이언트 훅 사용 시 필요한 Suspense 경계와, 향후 비동기 데이터 페칭 시 필요한 에러 처리를 공통 AsyncBoundary 컴포넌트로 추가했습니다.
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
timo Ready Ready Preview, Comment Jul 8, 2026 8:05am

@github-actions github-actions Bot requested review from jjangminii and yumin-kim2 July 7, 2026 17:11
@github-actions github-actions Bot added ⏰ Timo-web Timo 웹 서비스 ✨ Feature 새로운 기능(기능성) 구현 ♦️ 민아 민아상 labels Jul 7, 2026
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

ErrorBoundary 클래스 컴포넌트와 이를 활용하는 AsyncBoundary 컴포넌트가 신규 추가되었습니다. ErrorBoundary는 자식 렌더링 중 발생한 에러를 상태로 캡처하고 fallback(노드 또는 함수)을 렌더링하며 reset 기능을 제공합니다. AsyncBoundary는 children을 Suspense로 감싸고, errorFallback 존재 시에만 ErrorBoundary로 추가 래핑합니다.

Changes

AsyncBoundary/ErrorBoundary 공통 래퍼

Layer / File(s) Summary
ErrorBoundary 클래스 컴포넌트
apps/timo-web/components/boundary/ErrorBoundary.tsx
getDerivedStateFromError로 에러를 상태에 저장하고, reset 메서드로 에러 상태를 초기화하며, fallback(노드 또는 (error, reset) => ReactNode 함수)과 children을 조건에 따라 렌더링하는 클라이언트 전용 클래스 컴포넌트를 신규 정의.
AsyncBoundary 조합 래퍼
apps/timo-web/components/boundary/AsyncBoundary.tsx
childrenpendingFallback(기본 null)을 가진 Suspense로 감싸고, errorFallback이 제공된 경우에만 ErrorBoundary로 추가 래핑하는 컴포넌트를 신규 정의.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Parent
  participant AsyncBoundary
  participant ErrorBoundary
  participant Suspense
  Parent->>AsyncBoundary: children, pendingFallback, errorFallback 전달
  AsyncBoundary->>Suspense: children을 pendingFallback과 함께 래핑
  alt errorFallback 존재
    AsyncBoundary->>ErrorBoundary: Suspense 결과를 errorFallback과 함께 래핑
    ErrorBoundary-->>Parent: fallback 또는 children 렌더링
  else errorFallback 없음
    Suspense-->>Parent: Suspense 결과만 렌더링
  end
Loading

관련 이슈: #114 [FEAT] Suspense/ErrorBoundary 공통 래퍼(AsyncBoundary) 추가

제안 리뷰어: 짧고 명료한 두 파일이니, boundary 관련 코드를 자주 다루시는 분이 좋겠습니다.

서스펜스 감싸고 에러도 감싸고
리셋 한 번이면 다시 시작하고
경계 없던 코드에 경계가 생기니
당근처럼 든든한 컴포넌트 완성 🥕
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed 이슈 #114의 요구사항인 ErrorBoundary와 조건부 AsyncBoundary 구현, 타입 체크 통과가 모두 반영되었습니다.
Out of Scope Changes check ✅ Passed 요구된 공통 경계 컴포넌트 추가 외의 명확한 범위 이탈 변경은 보이지 않습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed AsyncBoundary와 Suspense/ErrorBoundary 공통 래퍼 추가라는 핵심 변경을 정확히 짚고 있습니다.
Description check ✅ Passed 설명이 AsyncBoundary와 ErrorBoundary 추가, 사용 의도, 구현 범위를 잘 설명해 변경 내용과 일치합니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web/114-async-boundary

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Timo Performance Report

Bundle Size — timo-web
라우트 크기 First Load JS
/[locale] 0 B 🟡 205.79 kB
/[locale]/focus 0 B 🟡 205.79 kB
/[locale]/home 0 B 🟡 205.79 kB
/[locale]/onboarding 0 B 🟡 205.79 kB
/[locale]/settings 0 B 🟡 205.79 kB
/[locale]/settings/account 0 B 🟡 205.79 kB
/[locale]/settings/policy 0 B 🟡 205.79 kB
/[locale]/statistics 0 B 🟡 205.79 kB
/[locale]/today 0 B 🟡 205.79 kB

공유 번들: 205.79 kB
🟢 < 200kB  |  🟡 < 350kB  |  🔴 ≥ 350kB (First Load JS · gzip)

Lighthouse — timo-web

⚠️ Lighthouse 결과를 가져오지 못했습니다.

Image Optimization — timo-web

public/ 디렉토리에 이미지가 없습니다.

측정 커밋: dd374b1

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/timo-web/components/boundary/AsyncBoundary.tsx`:
- Line 8: The errorFallback type in AsyncBoundary is duplicated from
ErrorBoundary’s ErrorFallback type, so export ErrorFallback from
ErrorBoundary.tsx and reuse that shared type in AsyncBoundary instead of
redefining the union. Update the props/type definitions around AsyncBoundary and
ErrorBoundary so both reference the same exported symbol, keeping the naming
rule intact while preventing the two files from drifting apart.

In `@apps/timo-web/components/boundary/ErrorBoundary.tsx`:
- Around line 20-46: `ErrorBoundary` only uses `getDerivedStateFromError` to
update UI state, but it does not report caught errors to monitoring tools. Add
`componentDidCatch` to the `ErrorBoundary` class and route the received `error`
and `errorInfo` to your logging/telemetry integration (for example Sentry),
while keeping the existing `reset` and `fallback` behavior unchanged.
- Around line 7-9: `ErrorFallback` is a union type alias, so rename it to follow
the type alias convention by adding the required `Types` suffix. Update the
`ErrorBoundary.tsx` type alias declaration and any references that use
`ErrorFallback` so the naming is consistent with the project rule for
union/tuple/literal aliases.
- Around line 16-18: Update ErrorBoundary to handle non-Error throw values
safely: in ErrorBoundary and its getDerivedStateFromError flow, stop assuming
the captured value is always an Error and accept unknown instead. Normalize the
caught value before storing or passing it to the fallback, using an instanceof
Error check to convert strings/objects into a safe Error shape so
ErrorBoundaryState and the fallback rendering stay robust.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4d57e387-8e6a-4d79-86b5-d69c505e3895

📥 Commits

Reviewing files that changed from the base of the PR and between cf96425 and a3a0fb8.

📒 Files selected for processing (2)
  • apps/timo-web/components/boundary/AsyncBoundary.tsx
  • apps/timo-web/components/boundary/ErrorBoundary.tsx

Comment thread apps/timo-web/components/boundary/AsyncBoundary.tsx Outdated
Comment thread apps/timo-web/components/boundary/ErrorBoundary.tsx Outdated
Comment thread apps/timo-web/components/boundary/ErrorBoundary.tsx
Comment thread apps/timo-web/components/boundary/ErrorBoundary.tsx
- ErrorFallbackTypes를 ErrorBoundary에서 export하여 AsyncBoundary가 재사용하도록 했습니다
- getDerivedStateFromError가 unknown 값을 안전하게 Error로 정규화하도록 수정했습니다
- componentDidCatch를 추가해 캐치된 에러를 Sentry로 전송하도록 했습니다

@jjangminii jjangminii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR 설명 자체가 "Suspense/ErrorBoundary 추가" + "리뷰에서 지적된 타입 중복·에러 처리 문제 수정"을 함께 담고 있어요. 타입 중복 제거(ErrorFallbackTypes export/재사용)와 unknown 에러 정규화(toError)를 리뷰 사이클 안에서 바로 해결하신 흐름이 깔끔했습니다.
캐치된 에러가 fallback UI로만 처리되고 어디에도 리포팅되지 않으면 실제 장애를 놓칠 수 있다는 문제를 인식하고, 기존 global-error.tsx와 동일한 Sentry 패턴을 그대로 재사용해 일관성 있게 연결하신 점이 좋아요. 새로운 리포팅 방식을 만들지 않고 기존 컨벤션을 따른 판단도 적절했습니다.

저도 Next.js 환경에서 바운더리 다루는 건 이번에 처음이라 많이 배워갑니다~

@yumin-kim2 yumin-kim2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로딩 중이랑 에러 났을 때 화면을 매번 따로 만들지 않게, 하나의 재사용 부품(AsyncBoundary)으로 표준화한 점이 좋은 것 같습니다.👍 PR 보면서 덕분에 Suspense/ErrorBoundary에 대해 알 수 있었고 몰랐던 부분에 대해 공부도 할 수 있었씁니다 수고하셨어요!!!

@kimminna kimminna merged commit 7446f26 into develop Jul 8, 2026
15 checks passed
@kimminna kimminna deleted the feat/web/114-async-boundary branch July 8, 2026 09:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능(기능성) 구현 ⏰ Timo-web Timo 웹 서비스 ♦️ 민아 민아상

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] Suspense/ErrorBoundary 공통 래퍼(AsyncBoundary) 추가

3 participants